package deepr.src.pg;

import deepr.filter.DbFilterBehavior;
import deepr.core.*;
import deepr.filter.FilterParser;
import deepr.filter.FilterRelay;
import deepr.lib.FilterLib;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class PgFilter extends DbFilterBehavior {


    public StringBuilder like(FilterParser parser, String object, String property, String paramKey, Object value) {
        Assert.isTrue(value instanceof String, property + " like value must be string");
        parser.getFilterParams().put(paramKey, "%" + value + "%");
        return new StringBuilder().append(property).append(" LIKE #{").append(paramKey).append("}");
    }

    @Override
    public StringBuilder between(FilterParser parser, String object, String key, String paramKey, Object value) {
        Object[] v = parser.betweenActionValueValid(object, value);
        Object min = v[0];
        Object max = v[1];
        String key2 = parser.getParamKey(object + "_" + paramKey);
        String key3 = parser.getParamKey(object + "_" + paramKey);
        parser.getFilterParams().put(key2, min);
        parser.getFilterParams().put(key3, max);
        return new StringBuilder().append(key).append(" BETWEEN #{").append(key2).append("} AND #{").append(key3).append("}");
    }

    @Override
    public StringBuilder restAction(FilterParser parser, String object, String property, String paramKey, Object value, String action) {
        parser.getFilterParams().put(paramKey, value);
        return new StringBuilder().append(property).append(action).append("#{").append(paramKey).append("}");
    }

    @Override
    public StringBuilder jsonKey(FilterParser parser, String object, PropertyData propertyData, String property, String[] keys, Object value, String action) throws InvocationTargetException, NoSuchMethodException, InstantiationException, ParseException, IllegalAccessException {
        keys = FilterLib.concatJsonKey(keys);
        String paramKey = keys[1];
        String eleKey = keys[0];
        if (action.equals("like")) {
            return like(parser, object, eleKey, paramKey, value);
        }
        value = parser.handleJsonKeyValue(propertyData, eleKey, value);
        if (parser.getHandlerJsonKey() != null) {
            eleKey = parser.getHandlerJsonKey();
            parser.setHandlerJsonKey(null);
        }
        if (action.equals("between")) {
            return between(parser, object, eleKey, paramKey, value);
        }
        boolean valueIsArray = value.getClass().isArray();
        if (valueIsArray) {
            return valueArray(parser, object, eleKey, paramKey, value);
        }
        return restAction(parser, object, eleKey, paramKey, value, action);
    }

    @Override
    public StringBuilder jsonAt(FilterParser parser, String object, String property, String paramKey, Object value) {
        parser.getFilterParams().put(paramKey, value);
        return new StringBuilder().append(property).append("@>#{").append(paramKey).append("}");
    }


    @Override
    public StringBuilder valueArray(FilterParser parser, String object, String property, String paramKey, Object value) {
        parser.getFilterParams().put(paramKey, value);
        return new StringBuilder().append(property).append("=any(#{").append(paramKey).append("})");
    }

    @Override
    public StringBuilder propertyArray(FilterParser parser, String object, String property, String paramKey, Object value) {
        parser.getFilterParams().put(paramKey, value);
        return new StringBuilder().append("#{").append(paramKey).append("}=any(").append(property).append(")");
    }

    @Override
    public StringBuilder twoArray(FilterParser parser, String object, String property, String paramKey, Object value) {
        parser.getFilterParams().put(paramKey, value);
        return new StringBuilder().append(property).append(" && #{").append(paramKey).append("}");
    }

    public void appendToParent(FilterParser parser, Integer depth, String parent, PropertyData propertyData, StringBuilder sb) {
        String forProperty = propertyData.getForProperty();
        HashMap<String, FilterRelay> objMap = getDepthObjectFilter().getOrDefault(depth, new HashMap<>());
        FilterRelay foreign = objMap.get(parent);
        foreign.appendProperty(forProperty, sb, parser.getJoiner());
        objMap.put(parent, foreign);
        getDepthObjectFilter().put(depth, objMap);
    }

    public StringBuilder getJsonArrayFilter(FilterParser parser, FilterRelay filterRelay) {
        HashMap<String, StringBuilder> jsonArrayFilter = filterRelay.getJsonArrayFilter();
        if (jsonArrayFilter.size() == 0)
            return new StringBuilder();
        StringBuilder from = new StringBuilder();
        StringBuilder sb2 = new StringBuilder();
        for (Map.Entry<String, StringBuilder> foreignEntry : jsonArrayFilter.entrySet()) {
            String fJK = foreignEntry.getKey();
            FilterLib.appendJoiner(from, ",");
            from.append("JSONB_ARRAY_ELEMENTS(").append(fJK).append(") AS ").append(fJK).append("_ele");
            StringBuilder fJKSb = foreignEntry.getValue();
            FilterLib.appendJoiner(sb2, parser.getJoiner());
            sb2.append(fJKSb);
        }
        return new StringBuilder().append("EXISTS(SELECT 1 FROM ").append(from).
                append(" WHERE ").append(sb2).append(" LIMIT 1)");
    }

    @Override
    public StringBuilder getForeignFilter(FilterParser parser) {
        HashMap<Integer, HashMap<String, FilterRelay>> depthForeign = getDepthObjectFilter();
        int depth = depthForeign.size();
        log.info("filterMap: {}", depthForeign);
        for (int i = depth; i > 0; i--) {
            HashMap<String, FilterRelay> objectFilter = depthForeign.getOrDefault(i, new HashMap<>());
            for (Map.Entry<String, FilterRelay> entry : objectFilter.entrySet()) {
                String object = entry.getKey();
                FilterRelay filterRelay = entry.getValue();
                StringBuilder sb = getObjectFilter(parser, object, filterRelay);
                if (i == 1)
                    return sb;
                String parent = filterRelay.getParent();
                SchemaData parentSchema = SchemaManager.getSchemaData(parent);
                PropertyData me = parentSchema.getPropertyData(object);
                appendToParent(parser, i - 1, parent, me, sb);
            }
        }
        return new StringBuilder();
    }

    @Override
    public StringBuilder getObjectFilter(FilterParser parser, String object, FilterRelay filterRelay) {
        String joiner = parser.getJoiner();
        StringBuilder sb1 = super.getPropertyFilter(parser, filterRelay);
        StringBuilder sb2 = getJsonArrayFilter(parser, filterRelay);
        StringBuilder sb = new StringBuilder();
        if (sb1.length() > 0 && sb2.length() > 0) {
            sb1.append(joiner).append(sb2);
        } else if (sb2.length() > 0)
            sb1.append(sb2);
        String parent = filterRelay.getParent();
        if (parent == null)
            return sb1;
        SchemaData parentSchema = SchemaManager.getSchemaData(parent);
        PropertyData me = parentSchema.getFields().get(object);
        String forProperty = me.getForProperty();
        SchemaData meSchema = SchemaManager.getSchemaData(object);
        String myName = meSchema.getName();
        String primaryKey = meSchema.getPrimaryKey();
        sb.append(forProperty).append("=ANY(SELECT ").append(primaryKey).append(" FROM ").append(myName).append(" WHERE ").append(sb1).append(")");
        return sb;
    }

}
